# pip install DButils # 安装数据库连接池模块
import pymysql
from dbutils.pooled_db import PooledDB
from flask import Flask
import re
from common.error_handler import SqlError
from copy import deepcopy


class POOL:

    def __init__(self, app: Flask, db_config: dict):
        """
        :params: app flask app
        :params: db_config 数据库配置 类型为字典
        """
        self.app = app
        try:
            # 创建数据库连接池
            self.pool = PooledDB(
                creator=pymysql,  # 使用链接数据库的模块
                maxconnections=10,  # 连接池允许的最大连接数，0和None表示不限制连接数
                mincached=1,  # 初始化时，链接池中至少创建的空闲的链接，0表示不创建
                maxcached=2,  # 链接池中最多闲置的链接，0和None不限制
                blocking=True,  # 连接池中如果没有可用连接后，是否阻塞等待。True，等待；False，不等待然后报错
                ping=0,  # ping MySQL服务端，检查是否服务可用。# 如：0 = None = never
                **db_config)
        except Exception as e:
            app.logger.warning(f"{db_config['db']}-数据库连接池创建失败, 失败原因:{e}")
            raise SqlError(e)

    def fetchone(self, sql_str, *args):
        """
        :param sql_str 数据库sql
        :param args 可变参数，替换sql_str中的占位符，可不传
        :return: 返回查询结果的一条记录，类型是字典; 若未查询到，则返回None
        """
        conn = self.pool.connection()
        with conn.cursor(pymysql.cursors.DictCursor) as cursor:
            self.app.logger.info(f"执行sql: {sql_str}")
            if args:
                self.app.logger.info(f"占位符参数: {args}")
            try:
                cursor.execute(sql_str, args)
                data = cursor.fetchone()
                self.app.logger.info(f"sql执行结果: {data}")
            except Exception as e:
                cursor.close()
                conn.close()
                self.app.logger.warning(f"执行sql失败！- 失败信息: {e}")
                raise SqlError(e)
        conn.close()
        return data

    def fetchall(self, sql_str, *args):
        """
        :param sql_str 数据库sql
        :param args 可变参数，替换sql_str中的占位符，可不传
        :return: 返回查询结果的全部记录，类型是列表，列表元素为字典
        """
        conn = self.pool.connection()
        with conn.cursor(pymysql.cursors.DictCursor) as cursor:
            self.app.logger.info(f"执行sql: {sql_str}")
            if args:
                self.app.logger.info(f"占位符参数: {args}")
            try:
                cursor.execute(sql_str, args)
                data = cursor.fetchall()
                self.app.logger.info(f"sql执行结果: {data}")
            except Exception as e:
                cursor.close()
                conn.close()
                self.app.logger.warning(f"执行sql失败！- 失败信息: {e}")
                raise SqlError(e)
        conn.close()
        return data

    def execute_dml(self, sql_str, *args):
        """
        function: 执行insert、update、delete
        :param sql_str 数据库sql
        :param args 可变参数，替换sql_str中的占位符，可不传
        :return: 无返回
        """
        conn = self.pool.connection()
        with conn.cursor(pymysql.cursors.DictCursor) as cursor:
            self.app.logger.info(f"执行sql: {sql_str}")
            if args:
                self.app.logger.info(f"占位符参数: {args}")
            try:
                data = cursor.execute(sql_str, args)
                # 提交操作，我们配置连接是自动提交，所以下面提交步骤也可省略
                conn.commit()
                self.app.logger.info(f"sql执行结果: {data}")
            except Exception as e:
                cursor.close()
                conn.close()
                self.app.logger.warning(f"执行sql失败！- 失败信息: {e}")
                raise SqlError(e)
        conn.close()
        return data

    def insert_helper(self, table_name: str, insert_dict: dict):
        """
        插入数据
        :param table_name: 表名
        :param insert_dict: key-value 字典形式的数据  ex: {"id": 10, "name": "xpcs"}
        """
        insert_keys = ', '.join(insert_dict.keys())
        insert_value = tuple(insert_dict.values())
        insert_place_holder = ', '.join(["%s" for i in insert_value])
        sql_str = f"""insert into {table_name} ({insert_keys}) values ({insert_place_holder})"""
        self.execute_dml(sql_str, *insert_value)

    def select_helper(self, sql_str, condition_dict={}, limit_dict={}):
        """
        :param sql_str: 查询sql # 目前只支持单表，多表待后续扩展
        :param condition_dict: 条件字典 ex {"id": 1, "name": "xpcs}
        :param limit_dict:  分页字典约定固定格式 {"page_num": 1, "page_size": 10}
        :return: 数据列表 list

        # 数据兼容 2024年6月25日16:12:30  # 满足in和like查询 # 兼容调试完成
        {"id": 1, "name": ("like", "xpcs"), "score": ("in", [XX,XX])}

        """
        # 条件字典我们会处理，当多个sql同时使用字典会有问题，所以深复制一份，与service层深复制请求入参同理
        condition_dict = deepcopy(condition_dict)
        order_by_str = None
        condition_value = None

        # 处理sql，如果包含排序则将排序拿出，拼接条件，最后拼接排序
        if "order by " in sql_str:
            sql_str_list = re.split(pattern=" order by ", string=sql_str)
            sql_str = sql_str_list[0]
            order_by_str = sql_str_list[1]

        # 拼接条件
        if condition_dict:
            sql_str += " where "
            for k, v in condition_dict.items():
                if isinstance(v, str):
                    sql_str += str(k) + "=%s and "
                # 兼容 in和like 查询
                if isinstance(v, tuple):
                    if v[0] == "in":
                        sql_str += str(k) + " in %s and "
                        condition_dict[k] = v[1]
                    if v[0] == "like":
                        sql_str += str(k) + " like %s and "
                        condition_dict[k] = "%" + v[1] + "%"
            # 去除结尾and
            sql_str = re.sub(pattern=" and $", repl="", string=sql_str)
            condition_value = tuple(condition_dict.values())

        # 拼接排序
        if order_by_str:
            sql_str += " order by " + order_by_str

        # 分页字典约定固定格式 {"page_num": 1, "page_size": 10}
        if limit_dict:
            page_size = limit_dict.get("page_size")
            page_num = limit_dict.get("page_num")
            limit_str = f" limit {(page_num - 1) * page_size}, {page_size}"
            sql_str += limit_str
        if condition_value:
            return self.fetchall(sql_str, *condition_value)
        else:
            return self.fetchall(sql_str)


# 单例模式，全局连接均从这里取连接池
POOLS = {}


def init_pool(app: Flask):
    POOLS["DB_API_AUTO"] = POOL(app, app.config["DB_API_AUTO"])
    app.logger.info("初始化数据库连接池完成")


def get_pool(name) -> POOL:
    return POOLS.get(name, None)

